#ifndef _PHY_TRANSMISSION_H_
#define _PHY_TRANSMISSION_H_

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <string.h>

#include <uhd.h>

#include "srslte/srslte.h"
#include "srslte/intf/intf.h"

#include "../../../../communicator/cpp/communicator_wrapper.h"
#include "helpers.h"
#include "transceiver.h"

// ************************** Definition of macros *****************************
// Set the number of 0 samples padded before the slot so that we don't miss part of it. (This issue only happens with local USRPs)
#define FIX_TX_OFFSET_SAMPLES 20//1500 // Set the number of zeros to be padded before the slot. OBS.: 20 for SW FIR, 20 for HW FIR

// Number of padding zeros after the end of the last subframe. OBS.: 20 is the best value found so far.
#define NOF_PADDING_ZEROS 229//40

// Timed commands are used to specify at which time Tx/Rx frequency and gains are changed.
#define TX_TIMED_COMMAND_ENABLED 1

// This is the amount of time used to set the timed commands so that they are executed before the transmit at.
#define TX_TIME_ADVANCE_FOR_COMMANDS 500//350

// Maximum number of channels for LBT.
#define MAX_NUM_OF_CHANNELS 58

// Do never change this. It is set according to DARPA suggestions.
#define PHY_TX_LO_OFFSET -42.0e6 // TX local offset.

// ****************************** Debugging macros *****************************
// Enable the writing of samples into a file, this is only for debugging purposes.
#define WRITE_TX_SUBFRAME_INTO_FILE 0 // Enbale or disable dumping of Tx samples.

// This macro enables the time profilling of transferring controls from main to tx thread, time advance and UHD transfer time.
#define ENBALE_TX_PROFILLING 0

// By default Tx filtering is enabled in the CMakefile and can be disabled with the following command: "cmake -DENABLE_PHY_TX_FILTERING=OFF ../"
#if(ENABLE_PHY_TX_FILTERING==1)
#include "trx_filter.h"
#endif

// ***************************** INFO/DEBUG MACROS *****************************
#define ENABLE_PHY_TX_PRINTS 1 // If you want to disable only the logs generated by TX, then set this macro to 0.

#define PHY_TX_PRINT(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= 0) { \
  fprintf(stdout, "[PHY TX PRINT]: " _fmt, __VA_ARGS__); } } while(0)

#define PHY_TX_DEBUG(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= SRSLTE_VERBOSE_DEBUG) { \
  fprintf(stdout, "[PHY TX DEBUG]: " _fmt, __VA_ARGS__); } } while(0)

#define PHY_TX_INFO(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= SRSLTE_VERBOSE_INFO) { \
  fprintf(stdout, "[PHY TX INFO]: " _fmt, __VA_ARGS__); } } while(0)

#define PHY_TX_ERROR(_fmt, ...) do { fprintf(stdout, "[PHY TX ERROR]: " _fmt, __VA_ARGS__); } while(0)

#define PHY_TX_PRINT_TIME(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= 0) { \
  char date_time_str[30]; helpers_get_data_time_string(date_time_str); \
  fprintf(stdout, "[PHY TX PRINT]: %s - " _fmt, date_time_str, __VA_ARGS__); } } while(0)

#define PHY_TX_DEBUG_TIME(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= SRSLTE_VERBOSE_DEBUG) { \
  char date_time_str[30]; helpers_get_data_time_string(date_time_str); \
  fprintf(stdout, "[PHY TX DEBUG]: %s - " _fmt, date_time_str, __VA_ARGS__); } } while(0)

#define PHY_TX_INFO_TIME(_fmt, ...) do { if(ENABLE_PHY_TX_PRINTS && scatter_verbose_level >= SRSLTE_VERBOSE_INFO) { \
  char date_time_str[30]; helpers_get_data_time_string(date_time_str); \
  fprintf(stdout, "[PHY TX INFO]: %s - " _fmt, date_time_str, __VA_ARGS__); } } while(0)

#define PHY_TX_ERROR_TIME(_fmt, ...) do { char date_time_str[30]; helpers_get_data_time_string(date_time_str); \
  fprintf(stdout, "[PHY TX ERROR]: %s - " _fmt, date_time_str, __VA_ARGS__); } while(0)

// *************************** Definition of types *****************************
typedef struct {
  uint32_t phy_id;
  LayerCommunicator_handle phy_comm_handle;
  srslte_rf_t *rf;
  double competition_bw;
  double competition_center_freq;
  uint16_t rnti;
  bool use_std_carrier_sep;
  bool is_lbt_enabled;
  bool send_tx_stats_to_mac;
  bool add_tx_timestamp;
  int initial_subframe_index; // Set the subframe index number to be used to start from.
  float rf_amp;
  uint32_t trx_filter_idx;
  uint32_t nof_prb;
  uint32_t default_tx_channel;
  double default_tx_bandwidth;
  uint32_t radio_id;
  bool decode_pdcch;
  uint32_t node_id;
  uint32_t intf_id;
  uint32_t nof_ports;
  uint32_t bw_idx;
  float initial_tx_gain;
  bool phy_filtering;

  srslte_ofdm_t ifft;
  srslte_pcfich_t pcfich;
  srslte_pdcch_t pdcch;
  srslte_pdsch_t pdsch;
  srslte_pdsch_cfg_t pdsch_cfg;
  srslte_softbuffer_tx_t softbuffer;
  srslte_regs_t regs;
  srslte_ra_dl_dci_t ra_dl;
  srslte_ra_dl_grant_t grant;
  srslte_cell_t cell_enb;

  uint32_t last_nof_subframes_to_tx;
  uint32_t last_mcs;

  // Attribute and ID for encoding/transmission thread.
  pthread_attr_t tx_encoding_thread_attr;
  pthread_t tx_encoding_thread_id;
  // This variable is used to stop encoding/transmission thread.
  volatile sig_atomic_t run_tx_encoding_thread;

  // This basic controls stores the last configured values.
  basic_ctrl_t last_tx_basic_control;

  int number_of_tx_offset_samples;
  int sf_n_re;
  int sf_n_samples;

  cf_t *sf_buffer_eb;
  cf_t *output_buffer;
  cf_t *subframe_ofdm_symbols;
  cf_t *sf_symbols[SRSLTE_MAX_PORTS];

  cf_t *pss_signal;
  cf_t *pss_signal_end;

  float sch_signal0[SRSLTE_SCH_LEN];
  float sch_signal1[SRSLTE_SCH_LEN];
  float sss_signal[SRSLTE_SSS_LEN];

  srslte_chest_dl_t est;
  srslte_dci_location_t locations[SRSLTE_NSUBFRAMES_X_FRAME][30];

  // Mutex used to synchronize between main and encoding/transmission thread.
  pthread_mutex_t tx_basic_control_mutex;
  // Condition variable used to synchronize between main and encoding/transmission thread.
  pthread_cond_t tx_basic_control_cv;

  // Mutex used to control access to enviroment/scenario parameters.
  pthread_mutex_t tx_env_update_mutex;

  // Circular buffer handle for Tx basic control information.
  tx_cb_handle tx_basic_control_handle;

  // Flag used to inform if there was a change in the environment parameters.
  bool env_update;
  // Flag used to inform if there was a change in the comp. frequency parameter.
  bool competition_freq_updated;
  // Holds the center frequency used in the last basic control message received from upper layers.
  double tx_channel_center_frequency;

  timer_t tx_thread_timer_id;

  bool use_scatter_sync_seq;
  uint32_t pss_len;
  float pss_boost_factor;

  bool enable_eob_pss;

} phy_transmission_t;

// ************************** Declaration of functions *************************
int phy_transmission_start_thread(LayerCommunicator_handle handle, srslte_rf_t* const rf, transceiver_args_t* const args, uint32_t phy_id);

int phy_transmission_stop_thread(uint32_t phy_id);

int phy_transmission_init_thread_context(phy_transmission_t* const phy_transmission_ctx, LayerCommunicator_handle handle, srslte_rf_t* const rf, transceiver_args_t* const args);

void phy_transmission_init_context(phy_transmission_t* const phy_transmission_ctx, LayerCommunicator_handle handle, srslte_rf_t* const rf, transceiver_args_t* const args);

void phy_transmission_init_last_basic_control(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_init_cell_parameters(phy_transmission_t* const phy_transmission_ctx);

int phy_transmission_change_parameters(phy_transmission_t* const phy_transmission_ctx, basic_ctrl_t* const bc);

void *phy_transmission_work(void *h);

void phy_transmission_send_rx_statistics(LayerCommunicator_handle handle, phy_stat_t* const phy_tx_stat);

unsigned int phy_transmission_reverse(register unsigned int x);

uint32_t phy_transmission_prbset_to_bitmask(uint32_t nof_prb);

int phy_transmission_update_radl(phy_transmission_t* const phy_transmission_ctx, uint32_t mcs, uint32_t nof_prb);

int phy_transmission_init_buffers(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_free_buffers(phy_transmission_t* const phy_transmission_ctx);

int phy_transmission_base_init(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_free_base(phy_transmission_t* const phy_transmission_ctx);

int phy_transmission_set_tx_sample_rate(phy_transmission_t* const phy_transmission_ctx);

int phy_transmission_set_initial_tx_freq_and_gain(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_send_tx_statistics(phy_transmission_t* const phy_transmission_ctx, phy_stat_t* const phy_tx_stat, int ret);

void phy_transmission_push_tx_basic_control_into_container(basic_ctrl_t* const basic_ctrl);

void phy_transmission_change_allocation(phy_transmission_t* const phy_transmission_ctx, uint32_t req_mcs, uint32_t req_bw_idx);

uint32_t phy_transmission_calculate_nof_subframes(uint32_t mcs, uint32_t bw_idx, uint32_t length);

uint32_t phy_transmission_get_tb_size(uint32_t bw_idx, uint32_t mcs);

void phy_transmission_update_environment(uint32_t phy_id, environment_t* const env_update);

void phy_transmission_set_competiton_center_freq(phy_transmission_t* const phy_transmission_ctx, double competition_center_freq);

double phy_transmission_get_competiton_center_freq(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_set_competiton_bw(phy_transmission_t* const phy_transmission_ctx, double competition_bw);

double phy_transmission_get_competiton_bw(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_set_env_update(phy_transmission_t* const phy_transmission_ctx, bool env_update);

bool phy_transmission_get_env_update(phy_transmission_t* const phy_transmission_ctx);

double phy_transmission_calculate_channel_center_frequency(phy_transmission_t* const phy_transmission_ctx, float tx_bandwidth, uint32_t tx_channel);

int phy_transmission_validate_tb_size(basic_ctrl_t* const bc, uint32_t bw_idx, uint32_t phy_id);

timer_t* phy_transmission_get_timer_id(uint32_t phy_id);

bool phy_transmission_timedwait_and_pop_tx_basic_control_from_container(phy_transmission_t* const phy_transmission_ctx, basic_ctrl_t* const basic_ctrl);

int phy_transmission_change_timed_parameters(phy_transmission_t* const phy_transmission_ctx, basic_ctrl_t* const bc);

int phy_transmission_change_non_timed_parameters(phy_transmission_t* const phy_transmission_ctx, basic_ctrl_t* const bc);

bool phy_transmission_get_env_update_comp_freq(phy_transmission_t* const phy_transmission_ctx);

void phy_transmission_set_env_update_comp_freq(phy_transmission_t* const phy_transmission_ctx, bool competition_freq_updated);

#endif // _PHY_TRANSMISSION_H_
